package com.dayuanit.dy17.atm.service.impl;

import com.dayuanit.dy17.atm.config.AtmPropertiesConfig;
import com.dayuanit.dy17.atm.domain.FlowDomain;
import com.dayuanit.dy17.atm.dto.CardDTO;
import com.dayuanit.dy17.atm.dto.FlowDTO;
import com.dayuanit.dy17.atm.entity.Card;
import com.dayuanit.dy17.atm.entity.Flow;
import com.dayuanit.dy17.atm.entity.TransferOrder;
import com.dayuanit.dy17.atm.exception.BizException;
import com.dayuanit.dy17.atm.mapper.CardMapper;
import com.dayuanit.dy17.atm.mapper.FlowMapper;
import com.dayuanit.dy17.atm.mapper.TransferOrderMapper;
import com.dayuanit.dy17.atm.service.ICardService;
import com.dayuanit.dy17.atm.service.IRedisService;
import com.dayuanit.dy17.atm.util.CardUtils;
import com.dayuanit.dy17.atm.util.DateUtils;
import com.dayuanit.dy17.atm.util.MoenyUtils;
import com.dayuanit.dy17.atm.util.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.*;

@Service
public class CardServiceImpl implements ICardService {

    public CardServiceImpl() {
        System.out.println("---------CardServiceImpl---------");
    }

    @Autowired
    private CardMapper cardMapper;

    @Autowired
    private FlowMapper flowMapper;

    @Autowired
    private TransferOrderMapper transferOrderMapper;

    @Resource
    private AtmPropertiesConfig atmPropertiesConfig;

    @Autowired
    @Qualifier(value="redisServiceTmpleteImpl")
    private IRedisService redisService;

    @Override
    public void openAccount(long userId, String password, String confirmPassword) {
        //业务逻辑
        // 密码非空校验
        if (null == password || null == confirmPassword) {
            throw new BizException("请输入密码");
        }

        // 密码长度校验
        if (password.length() < 3) {
            throw new BizException("密码长度不合法");
        }

        // 两次密码校验
        if (!password.equals(confirmPassword)) {
            throw new BizException("两次密码不相等");
        }

        Card card = new Card();
        card.setBalance(0);
        card.setCardNum(CardUtils.createCardNum());
        card.setCreateTime(new Date());
        card.setPassword(password);
        card.setUserId(userId);

        cardMapper.insert(card);
    }

    /**
     * 乐观锁
     *
     * 乐观锁和悲观锁的使用场景：
     * 在并发比较高的情况下，想保证成功率，可以使用悲观锁。
     * 在并发比较低的情况下，可以使用乐观锁。
     *
     * @param cardId
     * @param password
     * @param amount
     * @param userId
     */
    @Transactional
    @Override
    public void draw(long cardId, String password, String amount, long userId) {
        Card card = cardMapper.selectByPrimaryKey(cardId);

        int amountInt = new BigDecimal(amount).multiply(new BigDecimal(100)).intValue();
        int newBalance = card.getBalance() - amountInt;

        if (newBalance < 0) {
            throw new BizException("余额不足，请充值");
        }

        //更新余额  乐观锁 先比较 再更新
        int rows = cardMapper.updateBalance(cardId, newBalance, card.getBalance());
        if (1 != rows) {
            throw new BizException("取款失败");
        }

        // 增加流水
        Flow flow = new Flow();
        flow.setAmount(amountInt);
        flow.setCardId(cardId);
        flow.setCreateTime(new Date());
        flow.setFlowType(1);
        flow.setRemark("存款");
        flow.setUserId(userId);

        rows = flowMapper.insert(flow);
        if (1 != rows) {
            throw new BizException("存款失败");
        }
    }

    /**
     * 在写service的时候，要先保证主体业务能够正常执行，一些细节可以延后开发。
     * 在细化代码的时候，包括：参数的正常校验，比如非空、格式、以及大小等。
     * Service里如果有多个修改语句，那么就要考虑是否要加事务！！时刻提醒自己！
     *
     * Service方法要搞定两个大问题:事务和并发。
     * 如果有多个修改sql语句就要考虑加事务了，自己可以故意写个异常，看事务是否生效。
     * 另外，还要测试一下，在多线程的情况下，你的service方法是否运行正常，看一下数据在多线程情况下，是否出现不一致的问题。
     *
     * 解决方法：事务就打标签即可。
     * 并发：加锁，但要使用数据的悲观锁，也就是for update，这就是行锁，行锁的粒度小，并发度高，性能好。
     * 万不可使用JVM锁，它会将方法变为串行，性能很低；且在分布式情况下，无法保证资源的互斥性。
     *
     * 总结service方法：参数的正常体检(跟具体的业务挂钩)，事务，并发。把这三个问题搞定了，那么service方法也就没啥大问题了。
     *
     * @param cardId
     * @param password
     * @param amount
     * @param userId
     */
    @Transactional
    @Override
    public void deposit(long cardId, String password, String amount, long userId) {
        // 参数体检  金额大于0  密码正确 卡号要存在
        if (null == password || "".equals(password)) {
            throw new BizException("请输入密码");
        }

        if (null == amount || "".equals(amount)) {
            throw new BizException("请输入金额");
        }

        //关于金额大小 这个规则就属于业务了 不同的业务规则也不相同  比如 每天的存款限额 每月限额 VIP客户的限额 普通用户限额
        if (Double.parseDouble(amount) <= 0) {
            throw new BizException("存款金额必须大于0");
        }

        // 查找银行卡 悲观锁 也就是行锁
        Card card = cardMapper.getById4Lock(cardId);
        if (null == card) {
            throw new BizException("银行卡不存在");
        }

        //银行卡所属人的校验
        if (card.getUserId() != userId) {
            throw new BizException("你无权操作此银行卡");
        }

        //校验密码
        if (!card.getPassword().equals(password)) {
            throw new BizException("密码错误");
        }

        // 修改余额
        int balance = card.getBalance();

        BigDecimal bigDecimal = new BigDecimal(amount);
        int depositAmount = bigDecimal.multiply(new BigDecimal(100)).intValue();

        int newBalance = balance + depositAmount;

        //设置新的余额
        card.setBalance(newBalance);

        //更新余额
        int rows = cardMapper.updateByPrimaryKey(card);
        if (1 != rows) {
            throw new BizException("存款失败");
        }

        // 增加流水
        Flow flow = new Flow();
        flow.setAmount(depositAmount);
        flow.setCardId(cardId);
        flow.setCreateTime(new Date());
        flow.setFlowType(1);
        flow.setRemark("存款");
        flow.setUserId(userId);

        rows = flowMapper.insert(flow);
        if (1 != rows) {
            throw new BizException("存款失败");
        }



    }

    @Transactional
    @Override
    public void transfer(long cardOutId, String cardInNumber, String password, String amount, long userId) {
        // 参数校验
        if (null == cardInNumber || "".equals(cardInNumber)) {
            throw new BizException("请输入转入卡号");
        }

        if (null == password || "".equals(password)) {
            throw new BizException("请输入密码");
        }

        if (null == amount || "".equals(amount)) {
            throw new BizException("请输入转账金额");
        }

        if (Double.parseDouble(amount) <= 0) {
            throw new BizException("转账金额不能小于0");
        }

        // 转出 修改余额
        Card cardOut = cardMapper.selectByPrimaryKey(cardOutId);
        if (null == cardOut) {
            throw new BizException("转出卡不存在");
        }

        if (!cardOut.getPassword().equals(password)) {
            throw new BizException("密码不正确");
        }

        if (cardOut.getUserId() != userId) {
            throw new BizException("你无权操作此卡");
        }

        int transferAmount = new BigDecimal(amount).multiply(new BigDecimal(100)).intValue();

        int newBalance = cardOut.getBalance() - transferAmount;

        if (newBalance < 0) {
            throw new BizException("余额不足，请充值");
        }

        cardOut.setBalance(newBalance);

        int rows = cardMapper.updateByPrimaryKey(cardOut);
        if (1 != rows) {
            throw new BizException("转账失败");
        }

        // 增加转出流水
        Flow flow = new Flow();
        flow.setAmount(transferAmount);
        flow.setCardId(cardOut.getId());
        flow.setCreateTime(new Date());
        flow.setFlowType(3);
        flow.setRemark("转账支出");
        flow.setUserId(userId);

        rows = flowMapper.insert(flow);
        if (1 != rows) {
            throw new BizException("转账失败");
        }

        // 转入 修改余额
        Card cardIn = cardMapper.getByNumber(cardInNumber);
        if (null == cardIn) {
            throw new BizException("转入银行卡号不存在");
        }

        cardIn.setBalance(cardIn.getBalance() + transferAmount);
        rows = cardMapper.updateByPrimaryKey(cardIn);
        if (1 != rows) {
            throw new BizException("转账失败");
        }

        // 增加转入的流水
        flow = new Flow();
        flow.setAmount(transferAmount);
        flow.setCardId(cardIn.getId());
        flow.setCreateTime(new Date());
        flow.setFlowType(4);
        flow.setRemark("转账收入");
        flow.setUserId(cardIn.getUserId());

        rows = flowMapper.insert(flow);
        if (1 != rows) {
            throw new BizException("转账失败");
        }

    }

    @Override
    public Map<String, Object> queryFlow(long cardId, String password, int currentPage) {
        if (currentPage <= 0) {
            throw new BizException("无效的页码");
        }

        //TODO 参数校验 密码 卡号存在否
        Card card = cardMapper.selectByPrimaryKey(cardId);
        if (null == card) {
            throw new BizException("银行卡不存在");
        }

        //每页显示的条数
        int pageSize = 2;
        //sql语句查询符合条件的总条数
        int totalCount = flowMapper.countFlowByCardId(cardId);
        //总页数  注意：余数
        int totalPage = totalCount % pageSize == 0 ? totalCount / pageSize :  totalCount / pageSize + 1;

        //TODO 实际开发中，密码可能会有试错的次数限制。比如连续错三次后，要等12小时后再试。
        if (!card.getPassword().equals(password)) {
            throw new BizException("密码错误");
        }

        int offset = (currentPage - 1) * pageSize;

        List<FlowDomain> flowDomains = flowMapper.listFlowByCardId(cardId, offset, pageSize);
        List<FlowDTO> dtos = new ArrayList<>();

        for (FlowDomain domain : flowDomains) {
            FlowDTO flowDTO = new FlowDTO();
            dtos.add(flowDTO);

            flowDTO.setAmount(new BigDecimal(domain.getAmount()).divide(new BigDecimal(100)).setScale(2).toString());
            flowDTO.setCardNum(CardUtils.formatCardNum(domain.getCardNum()));
            flowDTO.setCreateTime(DateUtils.date2String2(domain.getCreateTime()));
            flowDTO.setRemark(domain.getRemark());
        }

        //由于需要返回总页数和每页的数据，所以将其放到Map集合中，返回即可。也就是封装。但是这种封装好吗？？
        //普通程序员
        Map<String, Object> pageDataMap = new HashMap<>();
        pageDataMap.put("totalPage", totalPage);
        pageDataMap.put("pageData", dtos);

        return pageDataMap;
    }

    @Override
    public PageHelper<List<FlowDTO>> queryFlow2(long cardId, String password, int currentPage) {
        if (currentPage <= 0) {
            throw new BizException("无效的页码");
        }

        // 参数校验 密码 卡号存在否
        Card card = cardMapper.selectByPrimaryKey(cardId);
        if (null == card) {
            throw new BizException("银行卡不存在");
        }

        //sql语句查询符合条件的总条数
        int totalCount = flowMapper.countFlowByCardId(cardId);

        if (!card.getPassword().equals(password)) {
            throw new BizException("密码错误");
        }

//        PageHelper pageHelper = PageHelper.init(totalCount, currentPage, pageSize);
        //通过泛型，防止类型错误。让代码阅读者很容易知道PageHelper存放的对象。
        PageHelper<List<FlowDTO>> pageHelper = PageHelper.init(totalCount, currentPage);

        //每页展示的数据
        List<FlowDomain> flowDomains = flowMapper.listFlowByCardId(cardId, pageHelper.offset(), PageHelper.default_page_size);
//        List<FlowDTO> dtos = new ArrayList<>(); //普通程序员

        //当我们知道集合要存放多少数据时，我们在定义集合的时候，就应该给上初始容量。目的是为了防止扩容。
        List<FlowDTO> dtos = new ArrayList<>(flowDomains.size());//优秀程序员

        for (FlowDomain domain : flowDomains) {
            FlowDTO flowDTO = new FlowDTO();
            dtos.add(flowDTO);

            flowDTO.setAmount(new BigDecimal(domain.getAmount()).divide(new BigDecimal(100)).setScale(2).toString());
            flowDTO.setCardNum(CardUtils.formatCardNum(domain.getCardNum()));
            flowDTO.setCreateTime(DateUtils.date2String2(domain.getCreateTime()));
            flowDTO.setRemark(domain.getRemark());
        }

        pageHelper.setPageData(dtos);
        return pageHelper;
    }

    @Override
    public List<CardDTO> queryMyCards(long userId) {
        List<Card> cards = cardMapper.listByUserId(userId);
        List<CardDTO> dtos = new ArrayList<>(cards.size());

        for (Card card : cards) {
            CardDTO cardDTO = new CardDTO();
            dtos.add(cardDTO);

            cardDTO.setBalance(MoenyUtils.fen2Yuan(card.getBalance()));
            cardDTO.setCardId(card.getId());
            cardDTO.setCardNum(CardUtils.formatCardNum(card.getCardNum()));
        }
        return dtos;
    }

    @Override
    public List<FlowDTO> queryFlowTop10(long userId) {
        // 先看缓存中是否有数据 若有则返；无则查DB,最后把查到的结果放到缓存中
        List<FlowDTO> flows = redisService.listTop10Flows(userId);
        if (!flows.isEmpty()) {
            System.out.println("TOP流水来自Redis缓存...");
            return flows;
        }

        System.out.println("TOP流水来自DB...");

        List<FlowDomain> flowDomains = flowMapper.listFlowByUserId(userId);
        List<FlowDTO> dtos = new ArrayList<>(flowDomains.size());

        for (FlowDomain domain : flowDomains) {
            FlowDTO flowDTO = new FlowDTO();
            dtos.add(flowDTO);

            flowDTO.setRemark(domain.getRemark());
            flowDTO.setCreateTime(DateUtils.date2String(domain.getCreateTime()));
            flowDTO.setCardNum(CardUtils.formatCardNum(domain.getCardNum()));
            flowDTO.setAmount(MoenyUtils.fen2Yuan(domain.getAmount()));
        }

        //放到redis缓存中
        redisService.saveTop10Flows(userId, dtos);
        return dtos;
    }

    @Transactional
    @Override
    public void delayTransfer(long cardOutId, String cardInNumber, String password, String amount, long userId) {
        //TODO 正常体检
        if (null == cardInNumber || "".equals(cardInNumber)) {
            throw new BizException("请输入转入卡号");
        }

        if (null == password || "".equals(password)) {
            throw new BizException("请输入密码");
        }

        if (null == amount || "".equals(amount)) {
            throw new BizException("请输入转账金额");
        }

        if (Double.parseDouble(amount) <= 0) {
            throw new BizException("转账金额不能小于0");
        }

        // 转出 修改余额
        Card cardOut = cardMapper.selectByPrimaryKey(cardOutId);
        if (null == cardOut) {
            throw new BizException("转出卡不存在");
        }

        if (!cardOut.getPassword().equals(password)) {
            throw new BizException("密码不正确");
        }

        if (cardOut.getUserId() != userId) {
            throw new BizException("你无权操作此卡");
        }

        int transferAmount = new BigDecimal(amount).multiply(new BigDecimal(100)).intValue();

        int newBalance = cardOut.getBalance() - transferAmount;

        if (newBalance < 0) {
            throw new BizException("余额不足，请充值");
        }

        cardOut.setBalance(newBalance);

        //修改余额
        int rows = cardMapper.updateByPrimaryKey(cardOut);
        if (1 != rows) {
            throw new BizException("转账失败");
        }

        // 增加转出流水
        Flow flow = new Flow();
        flow.setAmount(transferAmount);
        flow.setCardId(cardOut.getId());
        flow.setCreateTime(new Date());
        flow.setFlowType(3);
        flow.setRemark("转账支出");
        flow.setUserId(userId);

        rows = flowMapper.insert(flow);
        if (1 != rows) {
            throw new BizException("转账失败");
        }

        // 生成转账订单
        TransferOrder transferOrder = new TransferOrder();
        transferOrder.setAmount(transferAmount);
        transferOrder.setCardIdOut(cardOut.getId());
        transferOrder.setCardInNum(cardInNumber);
        transferOrder.setCreateTime(new Date());
        transferOrder.setModifyTime(transferOrder.getCreateTime());
        transferOrder.setPaymentTime(DateUtils.plusDate(atmPropertiesConfig.getDelayTime()));// 计算
        transferOrder.setStatus(1);
        transferOrder.setUserId(userId);
        rows = transferOrderMapper.insert(transferOrder);
        if (1 != rows) {
            throw new BizException("转账失败");
        }
    }
}
